<?php
class DBMagic {
	protected $db;
	private $table;
	private $t;
	private $sql;
	private $sql_where = '';
	private $sql_ext = '';
	private $sql_field = '';
	private $sql_orderby = '';
	private $sql_groupby;
	private $sql_limit = '';
	private $sql_join = array ();
	private $sql_table;
	
	const ALIAS_TABLE_NAME = '>';
	const METHOD_PREFIX = ':';
	//private $sql_map;
	

	function __construct( $name = 'default' ) {
		$this->db = Mid::db ( $name );
		if (DEBUG === TRUE) {
			echo '--- Model construct!' . '<br/>';
		}
	}
	
	/**
	 * Reset all var, except table and t
	 *
	 */
	private function reset() {
		$this->sql = '';
		$this->sql_where = '';
		$this->sql_ext = '';
		$this->sql_field = '';
		$this->sql_orderby = '';
		$this->sql_limit = '';
		$this->sql_groupby = '';
		$this->sql_join = array ();
		$this->sql_table = '';
		$this->addTable ( $this->table, $this->t );
		//$this->data = '';
	}
	
	/**
	 * Set main table
	 *
	 * @param string $table >	table name
	 * @param string $t		>	alias name
	 * @return unknown
	 */
	function table($table, $t = null) {
		$this->table = $table;
		$this->t = $t;
		$this->addTable ( $table, $t );
		return $this;
	}
	
	/**
	 * Update some data 
	 *
	 * @param mixed $arr	>	update data map	
	 * @param mixed $where	>	where map
	 * @return unknown
	 */
	public function update($arr, $where = null) {
		if ($where)
			$this->where ( $where );
		if (is_array ( $arr )) {
			foreach ( $arr as $k => $v ) {
				list ( $key, $action ) = explode ( self::METHOD_PREFIX, $k );
				$key = str_replace ( '.', '`.`', $key );
				$setagc = array ();
				if (strpos ( $v, self::METHOD_PREFIX ) !== false) {
					switch ($action) {
						case "eq" :
							$setagc [] = "`{$key}`=" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "math" :
							$setagc [] = "`{$key}`=" . str_replace ( '%', "`{$key}`", $v );
							break;
						case "is" :
							$setagc [] = "`{$key}` IS " . $v === null ? 'NULL' : $v;
							break;
						default :
							$setagc [] = "`{$key}`=" . (is_int ( $v ) ? $v : "'{$v}'");
					}
				} else {
					$setagc [] = "`{$key}`=" . (is_int ( $v ) ? $v : "'{$v}'");
				}
				$setstr = join ( ',', $setagc );
			}
		} else {
			$setstr = $arr;
		}
		$sql = 'UPDATE `' . $this->table . '` SET' . $setstr . ' ' . $this->sql_where;
		$this->query ( $sql );
		return $this;
	}
	
	/**
	 * Insert data to array
	 *
	 * @param array $arr	>	insert data map
	 * @return object $this
	 */
	public function insert($arr) {
		if (! is_array ( $arr ))
			return false;
		foreach ( $arr as $k => $v ) {
			$key [] = $k;
			$value [] = is_int ( $v ) ? $v : "'{$v}'";
		}
		$sql = 'INSERT INTO ' . $this->table . '(`' . join ( '`,`', $key ) . '`) VALUES(' . join ( ',', $value ) . ')';
		$this->query ( $sql );
		return $this;
	}
	
	/**
	 * Set order by Conditions
	 *
	 * @param mixed $arr	>	orderby map
	 * @return object $this
	 */
	public function orderBy($mixed) {
		$orderstr = '';
		if (is_array ( $mixed ) && ! empty ( $mixed )) {
			foreach ( $mixed as $k => $v ) {
				$orderagc [] = "{$k} {$v}";
			}
			$orderstr .= join ( ',', $orderagc );
		} elseif (is_string ( $mixed )) {
			$orderstr = $mixed;
		}
		if ($orderstr != '')
			$this->sql_orderby = ' ORDER BY ' . $orderstr;
		return $this;
	}
	
	/**
	 * Set group by Conditions
	 *
	 * @param string $str	>	groupby map
	 * @return object $this
	 */
	public function groupBy($mixed) {
		if (is_array ( $mixed ) && ! empty ( $mixed )) {
			$this->sql_groupby = ' GROUP BY ' . join ( ',', $mixed );
		} elseif (is_string ( $mixed )) {
			$this->sql_groupby = ' GROUP BY ' . $mixed;
		}
		return $this;
	}
	
	/**
	 * Set fileds you get
	 *
	 * @param array $arr	>	field map
	 * @return object $this
	 */
	public function field($arr = null) {
		if (! $arr) {
			$this->sql_field = "*";
		} else {
			if (is_array ( $arr )) {
				$this->sql_field = join ( ',', $arr );
			} else {
				$this->sql_field = $arr;
			}
		}
		return $this;
	}
	
	/**
	 * Set Query Conditions
	 *
	 * @param array $arr	>	where map
	 * @return object $this
	 */
	public function where($arr) {
		if (is_array ( $arr )) {
			$whereagc = array ();
			foreach ( $arr as $k => $v ) {
				@list ( $key, $action ) = explode ( self::METHOD_PREFIX, $k );
				$key = str_replace ( '.', '`.`', $key );
				if (strpos ( $k, self::METHOD_PREFIX ) !== false) {
					switch ($action) {
						case "eq" :
							$whereagc [] = "`{$key}`=" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "rf" :
							$whereagc [] = "`{$key}`!=" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "gt" :
							$whereagc [] = "`{$key}`>" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "lt" :
							$whereagc [] = "`{$key}`<" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "ge" :
							$whereagc [] = "`{$key}`>=" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "le" :
							$whereagc [] = "`{$key}`<=" . (is_int ( $v ) ? $v : "'{$v}'");
							break;
						case "in" :
							$whereagc [] = "`{$key}` IN " . (is_array ( $v ) ? join ( ',', $v ) : "({$v})");
							break;
						case "is" :
							$whereagc [] = "`{$key}` IS " . $v === null ? 'NULL' : $v;
							break;
						case "not" :
							$whereagc [] = "`{$key}` IS NOT " . $v === null ? 'NULL' : $v;
							break;
						case "F" :
							$whereagc [] = "`{$key}` = `" . str_replace ( '.', '`.`', $v ) . '`';
							break;
						default :
							$whereagc [] = "`{$key}`=" . (is_int ( $v ) ? $v : "'{$v}'");
					}
				} else {
					$whereagc [] = '`' . $key . '`=' . (is_int ( $v ) ? $v : "'{$v}'");
				}
			}
			$wherestr = join ( ' AND ', $whereagc );
		} elseif (is_string ( $arr )) {
			$wherestr = $arr;
		} else {
			$wherestr = '';
		}
		/*if (! $this->sql_where) {*/
		if ($wherestr != '')
			$this->sql_where = $wherestr;
			/*} else {
			$this->sql_where = '(' . $this->sql_where . ') AND ' . $wherestr;
		}*/
		return $this;
	}
	
	/**
	 * Set limit conditions as page
	 *
	 * @param int $page		>	page no
	 * @param int $num		>	number per page
	 * @return object $this
	 */
	public function page($page = 1, $num = 20) {
		if ($num === NULL) {
			$this->sql_limit = " LIMIT {$page}";
		} else {
			$start = ($page - 1) * $num;
			$this->sql_limit = " LIMIT {$start},{$num}";
		}
		return $this;
	}
	
	/**
	 * Set limit conditions
	 *
	 * @param int or string $start	>	start
	 * @param int $num				>	number you get
	 * @return object $this
	 */
	public function limit($start, $num = NULL) {
		if ($num === NULL) {
			$this->sql_limit = " LIMIT {$start}";
		} else {
			$this->sql_limit = " LIMIT {$start},{$num}";
		}
		return $this;
	}
	
	/**
	 * Set left join conditions
	 *
	 * @param string $table
	 * @param array $arr
	 * @return object $this
	 */
	public function leftJoin($table, $arr) {
		$this->joinTable ( 'left', $table, $arr );
		return $this;
	}
	
	/**
	 * Set left outer join conditions
	 *
	 * @param string $table
	 * @param array $arr
	 * @return object $this
	 */
	public function leftOuterJoin($table, $arr) {
		$this->joinTable ( 'leftouter', $table, $arr );
		return $this;
	}
	
	/**
	 * Set right join conditions
	 *
	 * @param string $table
	 * @param array $arr
	 * @return object $this
	 */
	public function rightJoin($table, $arr) {
		$this->joinTable ( 'right', $table, $arr );
		return $this;
	}
	
	/**
	 * Set right outer join conditions
	 *
	 * @param string $table
	 * @param array $arr
	 * @return object $this
	 */
	public function rightOuterJoin($table, $arr) {
		$this->joinTable ( 'rightouter', $table, $arr );
		return $this;
	}
	
	/**
	 * join table model
	 *
	 * @param string $type		>	join type
	 * @param string $table		>	table name
	 * @param array $arr		>	conditions array that after "ON"
	 */
	private function joinTable($type, $table, $arr) {
		$jointype = array ('left' => ' LEFT', 'leftouter' => ' LEFT OUTER', 'right' => ' RIGHT', 'rightouter' => ' RIGHT OUTER' );
		$on = array ();
		if (strpos ( $table, '>' ) !== FALSE) {
			$array = explode ( '>', $table );
			$table = '`' . $array [0] . '` ' . $array [1];
		}
		//$def = $this->tb ? $this->tb : $this->table;
		foreach ( $arr as $key => $value ) {
			$on [] = '`' . str_replace ( '.', '`.`', $key ) . '` = `' . str_replace ( '.', '`.`', $value ) . '`';
		}
		@$this->sql_join [$type] .= ' ' . $jointype [$type] . ' JOIN ' . $table . ' ON ' . join ( ' AND ', $on );
	}
	
	/**
	 * add Table that select from
	 *
	 * @param string $table		>	table name
	 * @param string $t			>	table alias name
	 * @return object $this
	 */
	public function addTable($table, $t) {
		if ($this->sql_table) {
			$this->sql_table .= ",`{$table}` `{$t}`";
		} else {
			$this->sql_table = "`{$table}` `{$t}`";
		}
		return $this;
	}
	
	/**
	 * add Field that select
	 *
	 * @param string $field		>	field name
	 * @param string $f			>	field alias name
	 * @return object $this
	 */
	public function addField($field, $f) {
		if ($this->sql_field) {
			$this->sql_field .= ",`{$field}` as $f";
		} else {
			$this->sql_field = "`{$field}` as $f";
		}
		return $this;
	}
	
	/**
	 * Make sql
	 *
	 * @return string $sql
	 */
	private function makeSql() {
		if (! $this->sql) {
			if (! $this->sql_field) {
				$this->sql_field = '*';
			}
			$join = '';
			if (! empty ( $this->sql_join )) {
				$join = join ( '', $this->sql_join );
			}
			if ($this->sql_where) {
				$where = ' WHERE ' . $this->sql_where;
			}
			$this->sql = 'SELECT ' . $this->sql_field . ' FROM ' . $this->sql_table . $join . $where . $this->sql_orderby . $this->sql_groupby . $this->sql_limit;
		}
		return $this->sql;
	}
	
	/**
	 * Find data finally
	 *
	 * @return array $data
	 */
	function find($mixed = NULL) {
		if ($mixed === NULL) {
			$sql = $this->makeSql ();
		} elseif (is_string ( $mixed )) {
			$sql = $mixed;
		} elseif (is_array ( $mixed )) {
			$this->setMap ( $mixed );
			$sql = $this->makeSql ();
		}
		$data = $this->fetchBySql ( $sql );
		return $data;
	}
	
	/**
	 * find data by map
	 *
	 * @todo Unfinished
	 * @param array $map
	 * @return object $this
	 */
	private function setMap($map = array()) {
		$this->field ( $map ['field'] );
		if (isset ( $map ['table'] ))
			$this->table ( $map ['table'] );
		if (isset ( $map ['where'] ))
			$this->where ( $map ['where'] );
		if (isset ( $map ['orderby'] ))
			$this->orderBy ( $map ['where'] );
		if (isset ( $map ['groupby'] ))
			$this->groupBy ( isset ( $map ['groupby'] ) );
		if (isset ( $map ['join'] ))
			$this->joinTable ( isset ( $map ['join'] ) );
		//return $this->find ();
	}
	
	/**
	 * Fetch data by sql
	 *
	 * @param string $sql
	 * @return array $data
	 */
	private function fetchBySql($sql) {
		$query = $this->db->query ( $sql );
		$array = array ();
		if (method_exists ( $this->db, 'fetch_all' )) {
			$array = $this->db->fetch_all ( $query );
		} else {
			while ( $gd = $this->db->fetch_array ( $query ) ) {
				$array [] = $gd;
			}
		}
		return $array;
	}
	
	/**
	 * Fetch a row from this model
	 *
	 * @return array $data
	 */
	function fetchRow() {
		$sql = $this->makeSql ();
		$array = $this->db->query_first ( $sql );
		return $array;
	}
	
	/**
	 * Fetch count from this model
	 *
	 * @return int rows number
	 */
	function fetchCount() {
		$sql = $this->makeSql ();
		$query = $this->db->query ( $sql );
		return $this->db->num_rows ( $query );
	}
	
	/**
	 * Fetch count from this model in no limit
	 *
	 * @return int count number
	 */
	function fetchAllCount() {
		$sql = $this->makeSql ();
		$sql = preg_replace ( array ('/SELECT (.*) FROM/i', '/LIMIT (.*)$/' ), array ('SELECT COUNT(*) as total_number FROM', '' ), $sql );
		$array = $this->db->query_first ( $sql );
		return $array ['total_number'];
	}
	
	/**
	 * Fetch query number
	 *
	 * @return int query number
	 */
	function fetchQueryNum() {
		return $this->db->querynum;
	}
	
	/**
	 * Query Sql And return $data;
	 *
	 * @param string $sql
	 * @return object $this
	 */
	function query($sql) {
		$this->db->query ( $sql );
		return $this;
	}
	
	function __destruct() {
		//
	}
	
	/**
	 * Call method	>	user fetchBy{Field} method to fetch data 
	 *
	 * @param string $methods
	 * @param args $args
	 * @return array $data
	 */
	function __call($methods, $args) {
		if (strtolower ( substr ( $methods, 0, 6 ) ) == 'findby') {
			$col = substr ( $methods, 6 );
			if (! $col)
				return array ();
			if (! isset ( $args [0] ))
				$args [0] = '';
			$sql = "SELECT * FROM `" . $this->table . "` WHERE `" . $col . "`" . (is_int ( $args [0] ) ? '=' . $args [0] : (is_null ( $args [0] ) ? 'IS NULL' : "='" . $args [0] . "'"));
			$array = $this->db->query_first ( $sql );
			return $array;
		}
	}
	
	/**
	 * Return instance of this class
	 *
	 * @return object this instance
	 */
	public function Instance( $name = 'default' ) {
		static $instance = NULL;
		if (! $instance) {
			$instance = new DBMagic ( $name );
		}
		return $instance;
	}
}
?>